/**
 * 
 */
package fhq.hcmute.qrcode.common;

import fhq.hcmute.qrcode.encoder.BitArray;
import fhq.hcmute.qrcode.reedsolomon.Monomial;
import fhq.hcmute.qrcode.reedsolomon.Polynomial;
import fhq.hcmute.qrcode.util.QRCodeException;
import static fhq.hcmute.qrcode.util.QRCodeException.*;

/**
 * @author Hien Nguyen Le
 * @Email: nglehien@gmail.com
 */
public final class Version {
	private int versionNumber;
	private int[] alignmentPatternPosition;
	private ECBlocks[] ecBlocks;
	private int totalNumberOfCodewords;

	// private static ErrorCorrectionLevel level;

	/**
	 * @param versionNumber
	 * @param alignmentPatternPosition
	 * @param ecBlocks
	 */
	private Version(int versionNumber, int[] alignmentPatternPosition,
			ECBlocks... ecBlocks) {
		super();
		this.versionNumber = versionNumber;
		this.alignmentPatternPosition = alignmentPatternPosition;
		this.ecBlocks = ecBlocks;
		int total = 0;
		int ecCodewords = ecBlocks[0].getECCodewordsPerBlock();
		ECB[] ecbArray = ecBlocks[0].getECBlocks();
		for (ECB ecBlock : ecbArray) {
			total += ecBlock.getECBlockCount()
					* (ecBlock.getNumberOfDataCodewords() + ecCodewords);
		}
		this.totalNumberOfCodewords = total;
	}

	/**
	 * Get current Error correction block based on Error correction Level
	 * 
	 * @return ECBlock
	 */
	public ECBlocks getCurrentECBlock(ErrorCorrectionLevel level) {
		return this.ecBlocks[level.ordinal()];
	}

	public int getTotalNumberOfCodewords() {
		return totalNumberOfCodewords;
	}

	public static Version getVersionByNumber(int number) {
		return Version.buildVersions()[number - 1];
	}

	public int getModules() {
		return this.versionNumber * 4 + 17;
	}

	public int getTotalDataCodewords(ErrorCorrectionLevel level) {
		return (this.totalNumberOfCodewords - ecBlocks[level.ordinal()]
				.getTotalECCodewords());
	}

	public int getNSyndromes(ErrorCorrectionLevel level) {
		// return Math.abs(getECCCodewords(level) - getCurrentECBlock(level).p);
		return Math.abs(getECCCodewords(level));
	}

	/**
	 * SEE MORE: ISO/IEC 18004:2006 page.81
	 * 
	 * @return Array bits of Version Information.
	 * @throws QRCodeException
	 */
	public BitArray encode() throws QRCodeException {
		BitArray bitArray = new BitArray();
		Polynomial gx = new Polynomial(new Monomial(1, 12),
				new Monomial(1, 11), new Monomial(1, 10), new Monomial(1, 9),
				new Monomial(1, 8), new Monomial(1, 5), new Monomial(1, 2),
				new Monomial(1, 0));
		if (this.versionNumber >= 7) {
			bitArray.append(6, versionNumber);
			Polynomial fx = new Polynomial();
			for (int i = bitArray.getSize() - 1; i >= 0; i--) {
				boolean c = bitArray.getBitAt(i);
				if (c) {
					fx.add(new Monomial(1, bitArray.getSize() - 1 - i));
				}
			}
			fx = fx.multiply(new Monomial(1, 18 - 6));
			fx.devide(gx);
			for (int i : fx.getRemainder().getArrayCoefficient(12)) {
				bitArray.append(1, i);
			}
		}
		return bitArray;
	}

	private static boolean compareDifferentBit(BitArray a, BitArray b) {
		if (a.getSize() == b.getSize()) {
			if (a.toString().equals(b.toString())) {
				return true;
			}
			int difference = 0;
			for (int i = 0; i < b.getSize(); i++) {
				if (a.getBitAt(i) != b.getBitAt(i)) {
					difference++;
					if (difference > 3) {
						return false;
					}
				}
			}
			return true;
		}
		return false;
	}

	public static Version decode(BitArray bitArray) throws QRCodeException {
		for (int i = 7; i <= 40; i++) {
			Version version = Version.getVersionByNumber(i);
			if (compareDifferentBit(version.encode(), bitArray)) {
				return version;
			}
		}
		throw new QRCodeException(INVALID_VERSION);
	}

	/*
	 * get Number of Error Correction Codewords base on Error Correction Level.
	 */
	public int getECCCodewords(ErrorCorrectionLevel level) {
		return ecBlocks[level.ordinal()].getECCodewordsPerBlock();
	}

	public static final class ECBlocks {
		private final int ecCodewordsPerBlock;
		private final ECB[] ecBlocks;
		private final int p;

		ECBlocks(int ecCodewordsPerBlock, ECB... ecBlocks) {
			this.ecCodewordsPerBlock = ecCodewordsPerBlock;
			this.ecBlocks = ecBlocks;
			this.p = 0;
		}

		ECBlocks(int ecCodewordsPerBlock, int p, ECB... ecBlocks) {
			this.ecCodewordsPerBlock = ecCodewordsPerBlock;
			this.ecBlocks = ecBlocks;
			this.p = p;
		}

		/**
		 * @return number of error correction codewords PER BLOCK
		 */
		public int getECCodewordsPerBlock() {
			return ecCodewordsPerBlock;
		}

		/**
		 * @return number of error correction blocks
		 */
		public int getNumBlocks() {
			int total = 0;
			for (ECB ecBlock : ecBlocks) {
				total += ecBlock.getECBlockCount();
			}
			return total;
		}

		/**
		 * @return number of error correction codewords
		 */
		public int getTotalECCodewords() {
			return ecCodewordsPerBlock * getNumBlocks();
		}

		public ECB[] getECBlocks() {
			return ecBlocks;
		}

		/**
		 * get the exponent of the first term for earch block.
		 * 
		 * @return array exponent
		 */
		public int[] getExponentForFirstTerm() {
			int[] result = new int[getECBlocks().length];
			for (int i = 0; i < result.length; i++) {
				result[i] = this.ecCodewordsPerBlock
						+ getECBlocks()[i].numberOfDataCodewords - 1;
			}
			return result;
		}

		public int getP() {
			return p;
		}

	}

	public static final class ECB {
		private final int ecBlockCount;
		private final int numberOfDataCodewords;

		ECB(int count, int dataCodewords) {
			this.ecBlockCount = count;
			this.numberOfDataCodewords = dataCodewords;
		}

		public int getNumberOfDataCodewords() {
			return numberOfDataCodewords;
		}

		public int getECBlockCount() {
			return ecBlockCount;
		}
	}

	public int getVersionNumber() {
		return versionNumber;
	}

	public int[] getAlignmentPatternPosition() {
		return alignmentPatternPosition;
	}

	public ECBlocks[] getEcBlocks() {
		return ecBlocks;
	}

	  private static Version[] buildVersions() {
		    return new Version[]{
		        new Version(1, new int[]{},
		            new ECBlocks(7, new ECB(1, 19)),
		            new ECBlocks(10, new ECB(1, 16)),
		            new ECBlocks(13, new ECB(1, 13)),
		            new ECBlocks(17, new ECB(1, 9))),
		        new Version(2, new int[]{6, 18},
		            new ECBlocks(10, new ECB(1, 34)),
		            new ECBlocks(16, new ECB(1, 28)),
		            new ECBlocks(22, new ECB(1, 22)),
		            new ECBlocks(28, new ECB(1, 16))),
		        new Version(3, new int[]{6, 22},
		            new ECBlocks(15, new ECB(1, 55)),
		            new ECBlocks(26, new ECB(1, 44)),
		            new ECBlocks(18, new ECB(2, 17)),
		            new ECBlocks(22, new ECB(2, 13))),
		        new Version(4, new int[]{6, 26},
		            new ECBlocks(20, new ECB(1, 80)),
		            new ECBlocks(18, new ECB(2, 32)),
		            new ECBlocks(26, new ECB(2, 24)),
		            new ECBlocks(16, new ECB(4, 9))),
		        new Version(5, new int[]{6, 30},
		            new ECBlocks(26, new ECB(1, 108)),
		            new ECBlocks(24, new ECB(2, 43)),
		            new ECBlocks(18, new ECB(2, 15),
		                new ECB(2, 16)),
		            new ECBlocks(22, new ECB(2, 11),
		                new ECB(2, 12))),
		        new Version(6, new int[]{6, 34},
		            new ECBlocks(18, new ECB(2, 68)),
		            new ECBlocks(16, new ECB(4, 27)),
		            new ECBlocks(24, new ECB(4, 19)),
		            new ECBlocks(28, new ECB(4, 15))),
		        new Version(7, new int[]{6, 22, 38},
		            new ECBlocks(20, new ECB(2, 78)),
		            new ECBlocks(18, new ECB(4, 31)),
		            new ECBlocks(18, new ECB(2, 14),
		                new ECB(4, 15)),
		            new ECBlocks(26, new ECB(4, 13),
		                new ECB(1, 14))),
		        new Version(8, new int[]{6, 24, 42},
		            new ECBlocks(24, new ECB(2, 97)),
		            new ECBlocks(22, new ECB(2, 38),
		                new ECB(2, 39)),
		            new ECBlocks(22, new ECB(4, 18),
		                new ECB(2, 19)),
		            new ECBlocks(26, new ECB(4, 14),
		                new ECB(2, 15))),
		        new Version(9, new int[]{6, 26, 46},
		            new ECBlocks(30, new ECB(2, 116)),
		            new ECBlocks(22, new ECB(3, 36),
		                new ECB(2, 37)),
		            new ECBlocks(20, new ECB(4, 16),
		                new ECB(4, 17)),
		            new ECBlocks(24, new ECB(4, 12),
		                new ECB(4, 13))),
		        new Version(10, new int[]{6, 28, 50},
		            new ECBlocks(18, new ECB(2, 68),
		                new ECB(2, 69)),
		            new ECBlocks(26, new ECB(4, 43),
		                new ECB(1, 44)),
		            new ECBlocks(24, new ECB(6, 19),
		                new ECB(2, 20)),
		            new ECBlocks(28, new ECB(6, 15),
		                new ECB(2, 16))),
		        new Version(11, new int[]{6, 30, 54},
		            new ECBlocks(20, new ECB(4, 81)),
		            new ECBlocks(30, new ECB(1, 50),
		                new ECB(4, 51)),
		            new ECBlocks(28, new ECB(4, 22),
		                new ECB(4, 23)),
		            new ECBlocks(24, new ECB(3, 12),
		                new ECB(8, 13))),
		        new Version(12, new int[]{6, 32, 58},
		            new ECBlocks(24, new ECB(2, 92),
		                new ECB(2, 93)),
		            new ECBlocks(22, new ECB(6, 36),
		                new ECB(2, 37)),
		            new ECBlocks(26, new ECB(4, 20),
		                new ECB(6, 21)),
		            new ECBlocks(28, new ECB(7, 14),
		                new ECB(4, 15))),
		        new Version(13, new int[]{6, 34, 62},
		            new ECBlocks(26, new ECB(4, 107)),
		            new ECBlocks(22, new ECB(8, 37),
		                new ECB(1, 38)),
		            new ECBlocks(24, new ECB(8, 20),
		                new ECB(4, 21)),
		            new ECBlocks(22, new ECB(12, 11),
		                new ECB(4, 12))),
		        new Version(14, new int[]{6, 26, 46, 66},
		            new ECBlocks(30, new ECB(3, 115),
		                new ECB(1, 116)),
		            new ECBlocks(24, new ECB(4, 40),
		                new ECB(5, 41)),
		            new ECBlocks(20, new ECB(11, 16),
		                new ECB(5, 17)),
		            new ECBlocks(24, new ECB(11, 12),
		                new ECB(5, 13))),
		        new Version(15, new int[]{6, 26, 48, 70},
		            new ECBlocks(22, new ECB(5, 87),
		                new ECB(1, 88)),
		            new ECBlocks(24, new ECB(5, 41),
		                new ECB(5, 42)),
		            new ECBlocks(30, new ECB(5, 24),
		                new ECB(7, 25)),
		            new ECBlocks(24, new ECB(11, 12),
		                new ECB(7, 13))),
		        new Version(16, new int[]{6, 26, 50, 74},
		            new ECBlocks(24, new ECB(5, 98),
		                new ECB(1, 99)),
		            new ECBlocks(28, new ECB(7, 45),
		                new ECB(3, 46)),
		            new ECBlocks(24, new ECB(15, 19),
		                new ECB(2, 20)),
		            new ECBlocks(30, new ECB(3, 15),
		                new ECB(13, 16))),
		        new Version(17, new int[]{6, 30, 54, 78},
		            new ECBlocks(28, new ECB(1, 107),
		                new ECB(5, 108)),
		            new ECBlocks(28, new ECB(10, 46),
		                new ECB(1, 47)),
		            new ECBlocks(28, new ECB(1, 22),
		                new ECB(15, 23)),
		            new ECBlocks(28, new ECB(2, 14),
		                new ECB(17, 15))),
		        new Version(18, new int[]{6, 30, 56, 82},
		            new ECBlocks(30, new ECB(5, 120),
		                new ECB(1, 121)),
		            new ECBlocks(26, new ECB(9, 43),
		                new ECB(4, 44)),
		            new ECBlocks(28, new ECB(17, 22),
		                new ECB(1, 23)),
		            new ECBlocks(28, new ECB(2, 14),
		                new ECB(19, 15))),
		        new Version(19, new int[]{6, 30, 58, 86},
		            new ECBlocks(28, new ECB(3, 113),
		                new ECB(4, 114)),
		            new ECBlocks(26, new ECB(3, 44),
		                new ECB(11, 45)),
		            new ECBlocks(26, new ECB(17, 21),
		                new ECB(4, 22)),
		            new ECBlocks(26, new ECB(9, 13),
		                new ECB(16, 14))),
		        new Version(20, new int[]{6, 34, 62, 90},
		            new ECBlocks(28, new ECB(3, 107),
		                new ECB(5, 108)),
		            new ECBlocks(26, new ECB(3, 41),
		                new ECB(13, 42)),
		            new ECBlocks(30, new ECB(15, 24),
		                new ECB(5, 25)),
		            new ECBlocks(28, new ECB(15, 15),
		                new ECB(10, 16))),
		        new Version(21, new int[]{6, 28, 50, 72, 94},
		            new ECBlocks(28, new ECB(4, 116),
		                new ECB(4, 117)),
		            new ECBlocks(26, new ECB(17, 42)),
		            new ECBlocks(28, new ECB(17, 22),
		                new ECB(6, 23)),
		            new ECBlocks(30, new ECB(19, 16),
		                new ECB(6, 17))),
		        new Version(22, new int[]{6, 26, 50, 74, 98},
		            new ECBlocks(28, new ECB(2, 111),
		                new ECB(7, 112)),
		            new ECBlocks(28, new ECB(17, 46)),
		            new ECBlocks(30, new ECB(7, 24),
		                new ECB(16, 25)),
		            new ECBlocks(24, new ECB(34, 13))),
		        new Version(23, new int[]{6, 30, 54, 78, 102},
		            new ECBlocks(30, new ECB(4, 121),
		                new ECB(5, 122)),
		            new ECBlocks(28, new ECB(4, 47),
		                new ECB(14, 48)),
		            new ECBlocks(30, new ECB(11, 24),
		                new ECB(14, 25)),
		            new ECBlocks(30, new ECB(16, 15),
		                new ECB(14, 16))),
		        new Version(24, new int[]{6, 28, 54, 80, 106},
		            new ECBlocks(30, new ECB(6, 117),
		                new ECB(4, 118)),
		            new ECBlocks(28, new ECB(6, 45),
		                new ECB(14, 46)),
		            new ECBlocks(30, new ECB(11, 24),
		                new ECB(16, 25)),
		            new ECBlocks(30, new ECB(30, 16),
		                new ECB(2, 17))),
		        new Version(25, new int[]{6, 32, 58, 84, 110},
		            new ECBlocks(26, new ECB(8, 106),
		                new ECB(4, 107)),
		            new ECBlocks(28, new ECB(8, 47),
		                new ECB(13, 48)),
		            new ECBlocks(30, new ECB(7, 24),
		                new ECB(22, 25)),
		            new ECBlocks(30, new ECB(22, 15),
		                new ECB(13, 16))),
		        new Version(26, new int[]{6, 30, 58, 86, 114},
		            new ECBlocks(28, new ECB(10, 114),
		                new ECB(2, 115)),
		            new ECBlocks(28, new ECB(19, 46),
		                new ECB(4, 47)),
		            new ECBlocks(28, new ECB(28, 22),
		                new ECB(6, 23)),
		            new ECBlocks(30, new ECB(33, 16),
		                new ECB(4, 17))),
		        new Version(27, new int[]{6, 34, 62, 90, 118},
		            new ECBlocks(30, new ECB(8, 122),
		                new ECB(4, 123)),
		            new ECBlocks(28, new ECB(22, 45),
		                new ECB(3, 46)),
		            new ECBlocks(30, new ECB(8, 23),
		                new ECB(26, 24)),
		            new ECBlocks(30, new ECB(12, 15),
		                new ECB(28, 16))),
		        new Version(28, new int[]{6, 26, 50, 74, 98, 122},
		            new ECBlocks(30, new ECB(3, 117),
		                new ECB(10, 118)),
		            new ECBlocks(28, new ECB(3, 45),
		                new ECB(23, 46)),
		            new ECBlocks(30, new ECB(4, 24),
		                new ECB(31, 25)),
		            new ECBlocks(30, new ECB(11, 15),
		                new ECB(31, 16))),
		        new Version(29, new int[]{6, 30, 54, 78, 102, 126},
		            new ECBlocks(30, new ECB(7, 116),
		                new ECB(7, 117)),
		            new ECBlocks(28, new ECB(21, 45),
		                new ECB(7, 46)),
		            new ECBlocks(30, new ECB(1, 23),
		                new ECB(37, 24)),
		            new ECBlocks(30, new ECB(19, 15),
		                new ECB(26, 16))),
		        new Version(30, new int[]{6, 26, 52, 78, 104, 130},
		            new ECBlocks(30, new ECB(5, 115),
		                new ECB(10, 116)),
		            new ECBlocks(28, new ECB(19, 47),
		                new ECB(10, 48)),
		            new ECBlocks(30, new ECB(15, 24),
		                new ECB(25, 25)),
		            new ECBlocks(30, new ECB(23, 15),
		                new ECB(25, 16))),
		        new Version(31, new int[]{6, 30, 56, 82, 108, 134},
		            new ECBlocks(30, new ECB(13, 115),
		                new ECB(3, 116)),
		            new ECBlocks(28, new ECB(2, 46),
		                new ECB(29, 47)),
		            new ECBlocks(30, new ECB(42, 24),
		                new ECB(1, 25)),
		            new ECBlocks(30, new ECB(23, 15),
		                new ECB(28, 16))),
		        new Version(32, new int[]{6, 34, 60, 86, 112, 138},
		            new ECBlocks(30, new ECB(17, 115)),
		            new ECBlocks(28, new ECB(10, 46),
		                new ECB(23, 47)),
		            new ECBlocks(30, new ECB(10, 24),
		                new ECB(35, 25)),
		            new ECBlocks(30, new ECB(19, 15),
		                new ECB(35, 16))),
		        new Version(33, new int[]{6, 30, 58, 86, 114, 142},
		            new ECBlocks(30, new ECB(17, 115),
		                new ECB(1, 116)),
		            new ECBlocks(28, new ECB(14, 46),
		                new ECB(21, 47)),
		            new ECBlocks(30, new ECB(29, 24),
		                new ECB(19, 25)),
		            new ECBlocks(30, new ECB(11, 15),
		                new ECB(46, 16))),
		        new Version(34, new int[]{6, 34, 62, 90, 118, 146},
		            new ECBlocks(30, new ECB(13, 115),
		                new ECB(6, 116)),
		            new ECBlocks(28, new ECB(14, 46),
		                new ECB(23, 47)),
		            new ECBlocks(30, new ECB(44, 24),
		                new ECB(7, 25)),
		            new ECBlocks(30, new ECB(59, 16),
		                new ECB(1, 17))),
		        new Version(35, new int[]{6, 30, 54, 78, 102, 126, 150},
		            new ECBlocks(30, new ECB(12, 121),
		                new ECB(7, 122)),
		            new ECBlocks(28, new ECB(12, 47),
		                new ECB(26, 48)),
		            new ECBlocks(30, new ECB(39, 24),
		                new ECB(14, 25)),
		            new ECBlocks(30, new ECB(22, 15),
		                new ECB(41, 16))),
		        new Version(36, new int[]{6, 24, 50, 76, 102, 128, 154},
		            new ECBlocks(30, new ECB(6, 121),
		                new ECB(14, 122)),
		            new ECBlocks(28, new ECB(6, 47),
		                new ECB(34, 48)),
		            new ECBlocks(30, new ECB(46, 24),
		                new ECB(10, 25)),
		            new ECBlocks(30, new ECB(2, 15),
		                new ECB(64, 16))),
		        new Version(37, new int[]{6, 28, 54, 80, 106, 132, 158},
		            new ECBlocks(30, new ECB(17, 122),
		                new ECB(4, 123)),
		            new ECBlocks(28, new ECB(29, 46),
		                new ECB(14, 47)),
		            new ECBlocks(30, new ECB(49, 24),
		                new ECB(10, 25)),
		            new ECBlocks(30, new ECB(24, 15),
		                new ECB(46, 16))),
		        new Version(38, new int[]{6, 32, 58, 84, 110, 136, 162},
		            new ECBlocks(30, new ECB(4, 122),
		                new ECB(18, 123)),
		            new ECBlocks(28, new ECB(13, 46),
		                new ECB(32, 47)),
		            new ECBlocks(30, new ECB(48, 24),
		                new ECB(14, 25)),
		            new ECBlocks(30, new ECB(42, 15),
		                new ECB(32, 16))),
		        new Version(39, new int[]{6, 26, 54, 82, 110, 138, 166},
		            new ECBlocks(30, new ECB(20, 117),
		                new ECB(4, 118)),
		            new ECBlocks(28, new ECB(40, 47),
		                new ECB(7, 48)),
		            new ECBlocks(30, new ECB(43, 24),
		                new ECB(22, 25)),
		            new ECBlocks(30, new ECB(10, 15),
		                new ECB(67, 16))),
		        new Version(40, new int[]{6, 30, 58, 86, 114, 142, 170},
		            new ECBlocks(30, new ECB(19, 118),
		                new ECB(6, 119)),
		            new ECBlocks(28, new ECB(18, 47),
		                new ECB(31, 48)),
		            new ECBlocks(30, new ECB(34, 24),
		                new ECB(34, 25)),
		            new ECBlocks(30, new ECB(20, 15),
		                new ECB(61, 16)))
		    };
		  }

}
